/*
 * TextSettings.java
 * Transform
 *
 * Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Flagstone Software Ltd. nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.flagstone.transform.text;


import java.io.IOException;

import com.flagstone.transform.MovieTag;
import com.flagstone.transform.MovieTypes;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.coder.Context;
import com.flagstone.transform.coder.SWFDecoder;
import com.flagstone.transform.coder.SWFEncoder;
import com.flagstone.transform.exception.IllegalArgumentRangeException;

/**
 * TextSettings allows you to control how individual text fields are rendered.
 *
 * <p>
 * There are four parameters that control how the text is rendered:
 * </p>
 * <ol>
 * <li>Advanced Rendering - whether the text is rendered using the advanced
 * anti-aliasing engine added in Flash 8.</li>
 * <li>Grid Alignment - how letters are aligned with respect to the pixel grid
 * used in LCD monitors.</li>
 * <li>Thickness - a parameter used to control the thickness of the line when
 * anti-aliasing is used.</li>
 * <li>Sharpness - a parameter used to control the sharpness of the line when
 * anti-aliasing is used.</li>
 * </ol>
 * <p>
 * The thickness and sharpness control the how the text is rendered:
 *
 * <pre>
 *    outsideCutoff = (0.5 * sharpness - thickness) * fontSize
 *    insideCutoff = (-0.5 * sharpness - thickness) * fontSize
 * </pre>
 *
 * Note that Adobe reports the results can be poor when the text is scaled by a
 * significant amount and so the default values of 0.0 should be used for the
 * thickness and sharpness values.
 * </p>
 */
public final class TextSettings implements MovieTag {
    /**
     * Grid specifies how letters are aligned with respect to the pixel grid on
     * a screen.
     */
    public enum Grid {
        /** Do not use grid fitting. */
        NONE,
        /** Align letters on pixel boundaries. */
        PIXEL,
        /** Align letters on 1/3 pixel boundaries. */
        SUBPIXEL
    }

    /** Format string used in toString() method. */
    private static final String FORMAT = "TextSettings: { identifier=%d;"
            + " useAdvanced=%s; grid=%s; thickness=%f; sharpness=%f}";

    /** The unique identifier of the text field. */
    private transient int identifier;
    /** Compound code for the rendering settings. */
    private transient int rendering;
    /** Control for the thickness of the line. */
    private transient int thickness;
    /** Control for the sharpness of the line. */
    private transient int sharpness;

    /**
     * Creates and initialises an TextSettings using values encoded in the Flash
     * binary format.
     *
     * @param coder
     *            an SWFDecoder object that contains the encoded Flash data.
     *
     * @throws IOException
     *             if an error occurs while decoding the data.
     */
    public TextSettings(final SWFDecoder coder) throws IOException {
        if ((coder.readUnsignedShort() & Coder.LENGTH_FIELD)
        		== Coder.IS_EXTENDED) {
            coder.readInt();
        }
        identifier = coder.readUnsignedShort();
        rendering = coder.readByte();
        thickness = coder.readInt();
        sharpness = coder.readInt();
        coder.readByte();
    }

    /**
     * Creates a TextSettings object with the specified values.
     *
     * @param uid
     *            the unique identifier of an existing text field.
     * @param advanced
     *            whether the advanced rendering engine will be used to display
     *            the text.
     * @param grid
     *            how letters are aligned with respect to the pixel grid.
     * @param thick
     *            the thickness used when anti-aliasing the text.
     * @param sharp
     *            the sharpness used when anti-aliasing the text.
     */
    public TextSettings(final int uid, final boolean advanced, final Grid grid,
            final float thick, final float sharp) {
        setIdentifier(uid);
        useAdvanced(advanced);
        setGrid(grid);
        setThickness(thick);
        setSharpness(sharp);
    }

    /**
     * Creates an TextSettings object and initialised it by copying the values
     * from an existing one.
     *
     * @param object
     *            a TextSettings object.
     */
    public TextSettings(final TextSettings object) {
        identifier = object.identifier;
        rendering = object.rendering;
        thickness = object.thickness;
        sharpness = object.sharpness;
    }

    /**
     * Get the unique identifier of the text definition that this object
     * applies to.
     *
     * @return the unique identifier of the text object.
     */
    public int getIdentifier() {
        return identifier;
    }

    /**
     * Sets the identifier of the text definition that this object applies to.
     *
     * @param uid
     *            the unique identifier of an DefineText, DefineText2 or
     *            DefineTextField object. Must be in the range 1..65535.
     */
    public void setIdentifier(final int uid) {
        if ((uid < 1) || (uid > Coder.USHORT_MAX)) {
            throw new IllegalArgumentRangeException(
                    1, Coder.USHORT_MAX, uid);
        }
        identifier = uid;
    }

    /**
     * Will the advanced text rendering engine, introduced in Flash 8
     * be used.
     *
     * @return true if advanced text rendering is used, false if the standard
     * rendering engine is used.
     */
    public boolean useAdvanced() {
        return (rendering & Coder.BIT6) != 0;
    }

    /**
     * Sets whether the advanced text rendering engine (true) or standard engine
     * (false) will be used to render the text.
     *
     * @param flag set true to select the advanced text rendering engine, false
     * for the standard rendering engine.
     */
    public void useAdvanced(final boolean flag) {
        rendering |= Coder.BIT6;
    }

    /**
     * Returns the alignment of letters with respect to the pixel grid.
     *
     * @return the alignment, either NONE, PIXEL or SUBPIXEL.
     */
    public Grid getGrid() {
        Grid alignment;

        if ((rendering & Coder.BIT4) > 0) {
            alignment = Grid.SUBPIXEL;
        } else if ((rendering & Coder.BIT3) > 0) {
            alignment = Grid.PIXEL;
        } else {
            alignment = Grid.NONE;
        }
        return alignment;
    }

    /**
     * Selects how the text letters will be aligned with respect to the pixel
     * grid used in LCD screens.
     *
     * @param alignment
     *            the alignment with respect to the pixel grid, either NONE,
     *            PIXEL or SUBPIXEL.
     */
    public void setGrid(final Grid alignment) {

        rendering &= ~(Coder.BIT3 | Coder.BIT4 | Coder.BIT5 | Coder.BIT6);

        switch (alignment) {
        case PIXEL:
            rendering |= Coder.BIT3;
            break;
        case SUBPIXEL:
            rendering |= Coder.BIT4;
            break;
        default:
            break;
        }
    }

    /**
     * Get the value used to control the thickness of a line when rendered.
     * May be set to 0.0 if the default anti-aliasing value will be used.
     *
     * @return the adjustment applied to the line thickness.
     */
    public float getThickness() {
        return Float.intBitsToFloat(thickness);
    }

    /**
     * Sets the value used to control the thickness of a line when rendered. May
     * be set to 0.0 if the default anti-aliasing value will be used.
     *
     * @param level
     *            the value of the thickness parameter used by the rendering
     *            engine.
     */
    public void setThickness(final float level) {
        thickness = Float.floatToIntBits(level);
    }

    /**
     * Get the value used to control the sharpness of a line when rendered.
     * May be set to 0.0 if the default anti-aliasing value will be used.
     *
     * @return the adjustment applied to the line sharpness.
     */
    public float getSharpness() {
        return Float.intBitsToFloat(sharpness);
    }

    /**
     * Sets the value used to control the sharpness of a line when rendered. May
     * be set to 0.0 if the default anti-aliasing value will be used.
     *
     * @param level
     *            the value of the sharpness parameter used by the rendering
     *            engine.
     */
    public void setSharpness(final float level) {
        this.sharpness = Float.floatToIntBits(level);
    }

    /** {@inheritDoc} */
    public TextSettings copy() {
        return new TextSettings(this);
    }

    @Override
    public String toString() {
        return String.format(FORMAT, identifier, String.valueOf(useAdvanced()),
                getGrid(), thickness / Coder.SCALE_16,
                sharpness / Coder.SCALE_16);
    }

    /** {@inheritDoc} */
    public int prepareToEncode(final Context context) {
        // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES
        return 14;
    }

    /** {@inheritDoc} */
    public void encode(final SWFEncoder coder, final Context context)
            throws IOException {
        // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 2 LINES
        coder.writeShort((MovieTypes.TEXT_SETTINGS << Coder.LENGTH_FIELD_SIZE)
                | 12);
        coder.writeShort(identifier);
        coder.writeByte(rendering);
        coder.writeInt(thickness);
        coder.writeInt(sharpness);
        coder.writeByte(0);
    }
}
